package com.ehotting.edsta.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.ehotting.eaf.core.constant.Constants;
import com.ehotting.eaf.core.constant.HttpStatus;
import com.ehotting.eaf.core.constant.SecurityConstants;
import com.ehotting.eaf.core.utils.ServletUtils;
import com.ehotting.eaf.core.utils.SignUtils;
import com.ehotting.eaf.core.utils.StringUtils;
import com.ehotting.eaf.redis.service.RedisService;
import com.ehotting.edsta.gateway.config.properties.IgnoreSignProperties;
import com.ehotting.edsta.gateway.config.properties.IgnoreWhiteProperties;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.net.URI;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

@Component
@Slf4j
public class SignatureFilter implements GlobalFilter, Ordered {

    // 排除过滤的 uri 地址，nacos自行添加
    @Autowired
    private IgnoreWhiteProperties ignoreWhite;

    @Autowired
    private IgnoreSignProperties ignoreSign;

    @Autowired
    private RedisService redisService;

    private static final int seconds = 5;
    private static final int maxCount = 5;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//        log.info("into inner class SignatureGatewayFilter>>>>>>>>>");
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        HttpMethod method = serverHttpRequest.getMethod();

        String ip = serverHttpRequest.getHeaders().getFirst("X-Real-IP");
//        log.info("ip:{}",ip);
        String url = serverHttpRequest.getURI().getPath();
        // 跳过不需要验证的路径

        if (StringUtils.isMatch("/file/**", url)
//                || StringUtils.isMatch("/system/**", url)
                || StringUtils.isMatch("/code/**", url)
                || StringUtils.matches(url, ignoreWhite.getWhites())
                || StringUtils.matches(url, ignoreSign.getIgnores())) {
            return chain.filter(exchange);
        }
        //时间戳
        String timestamp = serverHttpRequest.getHeaders().getFirst(SecurityConstants.TIMESTAMP);
        //校验串
        String checkTamperSign = serverHttpRequest.getHeaders().getFirst(SecurityConstants.SIGN);
//        log.info("前端提交的签名: " + checkTamperSign);
        Mono<Void> x = checkTimeStamp(exchange,timestamp);
        if (x != null) return x;

        //参数
        String params = "";
        String bodyStr = "";
        if (method == HttpMethod.POST || method == HttpMethod.PUT) {
            // 从请求里获取Post请求体
            bodyStr = exchange.getAttributeOrDefault("cachedRequestBody", "");
            params = bodyStr;
            if(method == HttpMethod.PUT && StringUtils.isEmpty(params)){
                // put请求的参数有可能放到有可能请求参数里
                MultiValueMap<String, String> requestQueryParams = serverHttpRequest.getQueryParams();
                params = requestQueryParamsToString(requestQueryParams);
            }
//            log.info("PUT OR POST params： " + params);
        } else if (method == HttpMethod.GET || method == HttpMethod.DELETE) {
//            MultiValueMap<String, String> requestQueryParams = serverHttpRequest.getQueryParams();
//            params = requestQueryParamsToString(requestQueryParams);
            params = "";
//            log.info("GET OR DELETE ciphertext  parameters： " + params);
        }
        //校验
//        log.info("转换后的params: " + params+url);
        if (!SignUtils.verifySign(params+url, checkTamperSign)) {
            return failResponse(exchange, "对不起，防篡改校验未通过，请求失败！");
        } else {
            //post请求的body需要解析之后再次封装，否则请求会报错
            if (method == HttpMethod.POST || method == HttpMethod.PUT) {
//                log.info("PUT OR POST bodyStr： " + bodyStr);
                URI uri = serverHttpRequest.getURI();
                ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();
                DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
                Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
                request = new ServerHttpRequestDecorator(request) {
                    @Override
                    public Flux<DataBuffer> getBody() {
                        return bodyFlux;
                    }
                };
                return chain.filter(exchange.mutate().request(request).build());
            }
        }

        // 重复提交校验
        Mono<Void> checkRepeatMono = checkRepeat(ip, url, checkTamperSign, exchange);
        if (checkRepeatMono != null) return checkRepeatMono;

        return chain.filter(exchange);
    }

    private Mono<Void> checkTimeStamp(ServerWebExchange exchange, String timestamp) {
        //校验时间：服务器时间和请求时间相差超过60秒，校验不通过
        if (StringUtils.isEmpty(timestamp)) {
            return failResponse(exchange, "请求头中没有时间戳字段");
        }
        long timeCheck = (System.currentTimeMillis() - Long.parseLong(timestamp)) / (1000);
        if (Math.abs(timeCheck) >= 60) {
//            log.info("Current Time Stamp: " + (System.currentTimeMillis()) / (1000));
//            log.info("timestamp: " + (System.currentTimeMillis()) / (1000));
            return failResponse(exchange, "当前电脑时间不对，误差不能超过1分钟，请求失败！");
        }
        return null;
    }

    private Mono<Void> checkRepeat(String ip,String requestURI,String sign,ServerWebExchange exchange) {
        // 同个ip重复访问同个接口
        String redisKey = Constants.TOO_FAST_KEY + ip + requestURI + sign;
//        log.info("redisKey:{}",redisKey);
        String count = redisService.getInt(redisKey);
        if (count == null) {
            //第一次访问
            redisService.setEx2(redisKey, String.valueOf(1), seconds, TimeUnit.SECONDS);
        } else {
            long i = Long.parseLong(count);
            if (i <= maxCount) {
                //加1
                redisService.incrBy(redisKey,1L);
            } else {
                //超出访问次数
                log.info("访问过快ip  ===> " + ip + " 且在   " + seconds + " 秒内超过最大限制  ===> " + maxCount + " 次数达到    ====> " + count);
                return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "操作太快了", HttpStatus.ERROR);
            }
        }
        return null;
    }


    private Mono<Void> failResponse(ServerWebExchange exchange, String msg) {
//        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.SIGNATURE_ERROR);
    }

    //将Map参数拼接位字符串
    private String requestQueryParamsToString(MultiValueMap<String, String> requestQueryParams) {
        if (CollectionUtils.isEmpty(requestQueryParams)){
            return "";
        }
        Map<String, String> map = new HashMap<>();
        for (String key : requestQueryParams.keySet()) {
            map.put(key, requestQueryParams.get(key).get(0));
        }
        return JSON.toJSONString(map);
    }

    private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
        //获取请求体
        Flux<DataBuffer> body = serverHttpRequest.getBody();
        AtomicReference<String> bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
//            log.info("buffer.readableByteCount():{}", buffer.readableByteCount());
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });
        return bodyRef.get();
    }

    private DataBuffer stringBuffer(String value) {
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }


    @Override
    public int getOrder() {
        return -20;
    }
}